#include "global.h"
#include "RageFileBasic.h"
#include "RageUtil.h"
#include "RageUtil_AutoPtr.h"

#include <cstddef>
#include <cstdint>

REGISTER_CLASS_TRAITS( RageFileBasic, pCopy->Copy() );

RageFileObj::RageFileObj()
{
	m_pReadBuffer = nullptr;
	m_pWriteBuffer = nullptr;

	ResetReadBuf();

	m_iReadBufAvail = 0;
	m_iWriteBufferPos = 0;
	m_iWriteBufferUsed = 0;
	m_bEOF = false;
	m_iFilePos = 0;
	m_bCRC32Enabled = false;
	m_iCRC32 = 0;
}

RageFileObj::RageFileObj( const RageFileObj &cpy ):
	RageFileBasic(cpy)
{
	/* If the original file has a buffer, copy it. */
	if( cpy.m_pReadBuffer != nullptr )
	{
		m_pReadBuffer = new char[BSIZE];
		memcpy( m_pReadBuffer, cpy.m_pReadBuffer, BSIZE );

		int iOffsetIntoBuffer = cpy.m_pReadBuf - cpy.m_pReadBuffer;
		m_pReadBuf = m_pReadBuffer + iOffsetIntoBuffer;
	}
	else
	{
		m_pReadBuffer = nullptr;
	}

	if( cpy.m_pWriteBuffer != nullptr )
	{
		m_pWriteBuffer = new char[cpy.m_iWriteBufferSize];
		memcpy( m_pWriteBuffer, cpy.m_pWriteBuffer, m_iWriteBufferUsed );
	}
	else
	{
		m_pWriteBuffer = nullptr;
	}

	m_iReadBufAvail = cpy.m_iReadBufAvail;
	m_bEOF = cpy.m_bEOF;
	m_iFilePos = cpy.m_iFilePos;
	m_iWriteBufferPos = cpy.m_iWriteBufferPos;
	m_iWriteBufferSize = cpy.m_iWriteBufferSize;
	m_iWriteBufferUsed = cpy.m_iWriteBufferUsed;
	m_bCRC32Enabled = cpy.m_bCRC32Enabled;
	m_iCRC32 = cpy.m_iCRC32;
}

RageFileObj::~RageFileObj()
{
	delete [] m_pReadBuffer;
	delete [] m_pWriteBuffer;
}

int RageFileObj::Seek( int iOffset )
{
	/* If we're already at the requested position, short circuit and don't flush
	 * our buffer. */
	if( iOffset == m_iFilePos )
		return m_iFilePos;

	m_bEOF = false;

	/* If we're calculating a CRC32, disable it. */
	m_bCRC32Enabled = false;

	/* Note that seeks do not flush the write buffer.  Instead, we flush lazily, on the next
	 * actual Write (or Flush).  Seek is not allowed to fail, and users should not need to
	 * flush before seeking to do proper error checking. */
	ResetReadBuf();

	int iPos = SeekInternal( iOffset );
	if( iPos != -1 )
	        m_iFilePos = iPos;
	return iPos;
}

int RageFileObj::Seek( int offset, int whence )
{
	switch( whence )
	{
	case SEEK_CUR:
		return Seek( Tell() + offset );
	case SEEK_END:
		offset += GetFileSize();
	}
	return Seek( (int) offset );
}

int RageFileObj::Read( void *pBuffer, size_t iBytes )
{
	int iRet = 0;

	while( !m_bEOF && iBytes > 0 )
	{
		if( m_pReadBuffer != nullptr && m_iReadBufAvail )
		{
			/* Copy data out of the buffer first. */
			int iFromBuffer = std::min( (int) iBytes, m_iReadBufAvail );
			memcpy( pBuffer, m_pReadBuf, iFromBuffer );
			if( m_bCRC32Enabled )
				CRC32( m_iCRC32, pBuffer, iFromBuffer );

			iRet += iFromBuffer;
			m_iFilePos += iFromBuffer;
			iBytes -= iFromBuffer;
			m_iReadBufAvail -= iFromBuffer;
			m_pReadBuf += iFromBuffer;

			pBuffer = (char *) pBuffer + iFromBuffer;
		}

		if( !iBytes )
			break;

		ASSERT( m_iReadBufAvail == 0 );

		/* If buffering is disabled, or the block is bigger than the buffer,
		 * read the remainder of the data directly into the desteination buffer. */
		if( m_pReadBuffer == nullptr || iBytes >= BSIZE )
		{
			/* We have a lot more to read, so don't waste time copying it into the
			 * buffer. */
			int iFromFile = this->ReadInternal( pBuffer, iBytes );
			if( iFromFile == -1 )
				return -1;

			if( iFromFile == 0 )
				m_bEOF = true;

			if( m_bCRC32Enabled )
				CRC32( m_iCRC32, pBuffer, iFromFile );
			iRet += iFromFile;
			m_iFilePos += iFromFile;
			return iRet;
		}

		/* If buffering is enabled, and we need more data, fill the buffer. */
		m_pReadBuf = m_pReadBuffer;
		int iGot = FillReadBuf();
		if( iGot == -1 )
			return iGot;
		if( iGot == 0 )
			m_bEOF = true;
	}

	return iRet;
}

int RageFileObj::Read( RString &sBuffer, int iBytes )
{
	sBuffer.reserve( iBytes != -1? iBytes: this->GetFileSize() );

	int iRet = 0;
	char buf[4096];
	while( iBytes == -1 || iRet < iBytes )
	{
		int ToRead = sizeof(buf);
		if( iBytes != -1 )
			ToRead  = std::min( ToRead, iBytes-iRet );

		const int iGot = Read( buf, ToRead );
		if( iGot == 0 )
			break;
		if( iGot == -1 )
			return -1;

		sBuffer.append( buf, iGot );
		iRet += iGot;
	}

	sBuffer.erase( sBuffer.begin()+iRet, sBuffer.end() );

	return iRet;
}

int RageFileObj::Read( void *pBuffer, size_t iBytes, int iNmemb )
{
	const int iRet = Read( pBuffer, iBytes*iNmemb );
	if( iRet == -1 )
		return -1;

	/* If we're reading 10-byte blocks, and we got 27 bytes, we have 7 extra bytes.
	 * Seek back.  XXX: seeking is very slow for eg. deflated ZIPs.  If the block is
	 * small enough, we may be able to stuff the extra data into the buffer. */
	const int iExtra = iRet % iBytes;
	Seek( Tell()-iExtra );

	return iRet/iBytes;
}

/* Empty the write buffer to disk.  Return -1 on error, 0 on success. */
int RageFileObj::EmptyWriteBuf()
{
	if( m_pWriteBuffer == nullptr )
		return 0;

	if( m_iWriteBufferUsed )
	{
		/* The write buffer may not align with the actual file, if we've seeked.  Only
		 * seek if needed. */
		bool bSeeked = (m_iWriteBufferPos+m_iWriteBufferUsed != m_iFilePos);
		if( bSeeked )
			SeekInternal( m_iWriteBufferPos );

		int iRet = WriteInternal( m_pWriteBuffer, m_iWriteBufferUsed );

		if( bSeeked )
			SeekInternal( m_iFilePos );
		if( iRet == -1 )
			return iRet;
	}

	m_iWriteBufferPos = m_iFilePos;
	m_iWriteBufferUsed = 0;
	return 0;
}

int RageFileObj::Write( const void *pBuffer, size_t iBytes )
{
	if( m_pWriteBuffer != nullptr )
	{
		/* If the file position has moved away from the write buffer, or the
		 * incoming data won't fit in the buffer, flush. */
		if( m_iWriteBufferPos+m_iWriteBufferUsed != m_iFilePos || m_iWriteBufferUsed + (int)iBytes > m_iWriteBufferSize )
		{
			int iRet = EmptyWriteBuf();
			if( iRet == -1 )
				return iRet;
		}

		if( m_iWriteBufferUsed + (int)iBytes <= m_iWriteBufferSize )
		{
			memcpy( m_pWriteBuffer+m_iWriteBufferUsed, pBuffer, iBytes );
			m_iWriteBufferUsed += iBytes;
			m_iFilePos += iBytes;
			if( m_bCRC32Enabled )
				CRC32( m_iCRC32, pBuffer, iBytes );
			return iBytes;
		}

		/* We're writing a lot of data, and it won't fit in the buffer.  We already
		 * flushed above, so m_iWriteBufferUsed; fall through and write the block normally. */
		ASSERT_M( m_iWriteBufferUsed == 0, ssprintf("%i", m_iWriteBufferUsed) );
	}

	int iRet = WriteInternal( pBuffer, iBytes );
	if( iRet != -1 )
	{
		m_iFilePos += iRet;
		if( m_bCRC32Enabled )
			CRC32( m_iCRC32, pBuffer, iBytes );
	}
	return iRet;
}

int RageFileObj::Write( const void *pBuffer, size_t iBytes, int iNmemb )
{
	/* Simple write.  We never return partial writes. */
	int iRet = Write( pBuffer, iBytes*iNmemb ) / iBytes;
	if( iRet == -1 )
		return -1;
	return iRet / iBytes;
}

int RageFileObj::Flush()
{
	int iRet = EmptyWriteBuf();
	if( iRet == -1 )
		return iRet;
	return FlushInternal();
}

void RageFileObj::EnableReadBuffering()
{
	if( m_pReadBuffer == nullptr )
		m_pReadBuffer = new char[BSIZE];
}

void RageFileObj::EnableWriteBuffering( int iBytes )
{
	if( m_pWriteBuffer == nullptr )
	{
		m_pWriteBuffer = new char[iBytes];
		m_iWriteBufferPos = m_iFilePos;
		m_iWriteBufferSize = iBytes;
	}
}

void RageFileObj::EnableCRC32( bool bOn )
{
	if( !bOn )
	{
		m_bCRC32Enabled = false;
		return;
	}

	m_bCRC32Enabled = true;
	m_iCRC32 = 0;
}

bool RageFileObj::GetCRC32( uint32_t *iRet )
{
	if( !m_bCRC32Enabled )
		return false;

	*iRet = m_iCRC32;
	return true;
}

/* Read up to the next \n, and return it in out.  Strip the \n.  If the \n is
 * preceded by a \r (DOS newline), strip that, too. */
int RageFileObj::GetLine( RString &sOut )
{
	sOut = "";

	if( m_bEOF )
	{
		return 0;
	}
	EnableReadBuffering();

	bool bGotData = false;
	for(;;)
	{
		bool bDone = false;

		/* Find the end of the block we'll move to out. */
		char *p = (char *) memchr( m_pReadBuf, '\n', m_iReadBufAvail );
		bool bReAddCR = false;
		if( p == nullptr )
		{
			/* Hack: If the last character of the buffer is \r, then it's likely that an
			 * \r\n has been split across buffers.  Move everything else, then move the
			 * \r to the beginning of the buffer and handle it the next time around the loop. */
			if( m_iReadBufAvail && m_pReadBuf[m_iReadBufAvail-1] == '\r' )
			{
				bReAddCR = true;
				--m_iReadBufAvail;
			}

			p = m_pReadBuf+m_iReadBufAvail; /* everything */
		}
		else
		{
			bDone = true;
		}

		if( p >= m_pReadBuf )
		{
			char *RealEnd = p;
			if( bDone && p > m_pReadBuf && p[-1] == '\r' )
			{
				--RealEnd; /* not including \r */
			}
			sOut.append( m_pReadBuf, RealEnd );

			if( bDone )
			{
				++p; /* skip \n */
			}
			const int iUsed = p-m_pReadBuf;
			if( iUsed )
			{
				m_iReadBufAvail -= iUsed;
				m_iFilePos += iUsed;
				bGotData = true;
				m_pReadBuf = p;
			}
		}

		if( bReAddCR )
		{
			ASSERT( m_iReadBufAvail == 0 );
			m_pReadBuf = m_pReadBuffer;
			m_pReadBuffer[m_iReadBufAvail] = '\r';
			++m_iReadBufAvail;
		}

		if( bDone )
			break;

		/* We need more data. */
		m_pReadBuf = m_pReadBuffer;

		const int iSize = FillReadBuf();

		/* If we've read data already, then don't mark EOF yet.  Wait until the
		 * next time we're called. */
		if( iSize == 0 && !bGotData )
		{
			m_bEOF = true;
			return 0;
		}
		if( iSize == -1 )
			return -1; // error
		if( iSize == 0 )
			break; // EOF or error
	}
	return bGotData? 1:0;
}

// Always use "\r\n".  Even though the program may be running on Unix, the
// files written to a memory card are likely to be edited using Windows.
//#if defined(WIN32)
#define NEWLINE "\r\n"
//#else
//#define NEWLINE "\n"
//#endif

int RageFileObj::PutLine( const RString &sStr )
{
	if( Write(sStr) == -1 )
		return -1;
	return Write( RString(NEWLINE) );
}

/* Fill the internal buffer.  This never marks EOF, since this is an internal, hidden
 * read; EOF should only be set as a result of a real read.  (That is, disabling buffering
 * shouldn't cause the results of AtEOF to change.) */
int RageFileObj::FillReadBuf()
{
	/* Don't call this unless buffering is enabled. */
	ASSERT( m_pReadBuffer != nullptr );

	/* The buffer starts at m_Buffer; any data in it starts at m_pReadBuf; space between
	 * the two is old data that we've read.  (Don't mangle that data; we can use it
	 * for seeking backwards.) */
	const int iBufAvail = BSIZE - (m_pReadBuf-m_pReadBuffer) - m_iReadBufAvail;
	ASSERT_M( iBufAvail >= 0, ssprintf("%p, %p, %i", static_cast<void*>(m_pReadBuf), static_cast<void*>(m_pReadBuffer), BSIZE ) );
	const int iSize = this->ReadInternal( m_pReadBuf+m_iReadBufAvail, iBufAvail );

	if( iSize > 0 )
		m_iReadBufAvail += iSize;

	return iSize;
}

void RageFileObj::ResetReadBuf()
{
	m_iReadBufAvail = 0;
	m_pReadBuf = m_pReadBuffer;
}

/*
 * Copyright (c) 2003-2004 Glenn Maynard
 * All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, and/or sell copies of the Software, and to permit persons to
 * whom the Software is furnished to do so, provided that the above
 * copyright notice(s) and this permission notice appear in all copies of
 * the Software and that both the above copyright notice(s) and this
 * permission notice appear in supporting documentation.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
 * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
 * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
 * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
 * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */

